package dryven.unittest;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import dryven.annotations.FormModel;
import dryven.model.binding.fieldset.Field;
import dryven.model.binding.fieldset.FieldGroup;
import dryven.model.binding.fieldset.FieldSet;
import dryven.model.binding.form.Form;
import dryven.model.binding.form.FormBinding;
import dryven.model.binding.form.FormDecoder;
import dryven.model.binding.form.FormGroup;
import dryven.model.binding.form.FormValidation;
import dryven.model.binding.proxy.Binding;
import dryven.model.binding.proxy.BindingProxy;
import dryven.model.validation.Validator;
import dryven.model.validation.ValidatorService;
import dryven.model.validation.di.DependencyInjectingValidatorService;
import dryven.request.controller.ControllerDescription;
import dryven.request.controller.paramtransform.ActionParameterBindCandidate;
import dryven.request.controller.paramtransform.ActionParameterFillCandidate;
import dryven.request.controller.paramtransform.ActionParameterTransformCandidate;
import dryven.request.controller.paramtransform.ActionParameterTransformFeedback;
import dryven.request.controller.paramtransform.ActionParameterTransformer;
import dryven.request.controller.paramtransform.CompositeActionParameterTransformer;
import dryven.request.controller.paramtransform.baseimpl.DefaultValueFiller;
import dryven.request.controller.paramtransform.baseimpl.FormValidationNullValueBinder;
import dryven.request.controller.paramtransform.baseimpl.PrimitiveTypeBinder;
import dryven.request.controller.paramtransform.baseimpl.RequestBinder;
import dryven.request.controller.paramtransform.baseimpl.UrlDecodeTransformer;
import dryven.request.controller.result.ActionResult;
import dryven.request.http.Request;
import dryven.request.http.Response;
import dryven.request.routing.DefaultRoutingService;
import dryven.request.routing.Router;
import dryven.request.routing.RoutingService;
import dryven.request.routing.routers.PackageRouter;
import dryven.unittest.formtest.FormController;
import dryven.unittest.mocks.BodyParamMockRequest;
import dryven.unittest.mocks.MockActionParameterTransformer;
import dryven.unittest.mocks.MockDatabaseConfigurationSource;
import dryven.unittest.mocks.MockPersistenceProvider;
import dryven.unittest.mocks.MockRequest;
import dryven.unittest.mocks.MockResponse;
import dryven.unittest.mocks.MockRoutingService;
import dryven.unittest.mocks.MockTranslator;
import dryven.unittest.mocks.MockDependencyService;
import dryven.unittest.models.ExampleModel;
import dryven.util.ArrayIterable;
import dryven.util.crypt.AESDecryption;
import dryven.util.crypt.AESGeneratedKey;
import junit.framework.TestCase;

public class FormTestCase extends TestCase {
	
	private ActionParameterTransformer createTransformer() {
		Iterable<ActionParameterTransformCandidate> transformers = new ArrayIterable<ActionParameterTransformCandidate>(new ActionParameterTransformCandidate[] {new UrlDecodeTransformer()});
		Iterable<ActionParameterBindCandidate> binders = new ArrayIterable<ActionParameterBindCandidate>(new ActionParameterBindCandidate[] {new PrimitiveTypeBinder(), new RequestBinder(), new FormValidationNullValueBinder()});
		Iterable<ActionParameterFillCandidate> fillers = new ArrayIterable<ActionParameterFillCandidate>(new ActionParameterFillCandidate[] {new DefaultValueFiller()});
		
		ActionParameterTransformer tr = new CompositeActionParameterTransformer(transformers,binders,fillers);
		return tr;
	}
	
	public void testForm() {
		//setup
		AESGeneratedKey aesKey = new AESGeneratedKey();
		String signSecret = "secret";
		BindingProxy proxy = new BindingProxy();
		
		Collection<Router> routers = new LinkedList<Router>();
		routers.add(new PackageRouter(new ArrayIterable<String>(new String[]{FormController.class.getPackage().getName()})));
		RoutingService rs = new DefaultRoutingService(routers, createTransformer(),null);
		Response response = new MockResponse();
		
		Binding<ActionResult> saveBinding = proxy.newBinding(proxy.byClass(FormController.class).save());
		ControllerDescription indexCD = new ControllerDescription(proxy.newBinding(proxy.byClass(FormController.class).index(null)));
		
		//create bad form
		Form form = new Form(
				proxy.newBinding(proxy.byObject(this).createFields()), 
				saveBinding, 
				null
			);
		form.injectDependencies(signSecret, aesKey, rs);
		form.setErrorAction(indexCD);
		
		//set http values
		Map<String, String> postValues = new HashMap<String, String>();
		postValues.put(Form.SignatureField, form.getSignature());
		
		boolean enteredInLoop = false;
		for (FormGroup g : form.getGroups()) {
			for (FormBinding b : g.getBindings()) {
				enteredInLoop = true;
				postValues.put(b.getName(), getIncorrectValueForFormBinding(b));
			}
		}
		assertTrue(enteredInLoop);

		System.out.println(form.getActionURL());
		
		Request r = new BodyParamMockRequest(form.getActionURL(), postValues);
		//call decoder
		TransformFeedback feedback = validateForm(aesKey, signSecret, r, rs);
		//assert results
		assertTrue(feedback.getParameter()==null);
		assertTrue(feedback.getRedirect().getAction().equals(indexCD.getAction()));
		
		//create good form
		form = new Form(
				proxy.newBinding(proxy.byObject(this).createFields()), 
				saveBinding, 
				null
			);
		form.injectDependencies(signSecret, aesKey, rs);
		form.setErrorAction(indexCD);
		
		//set http values
		postValues = new HashMap<String, String>();
		postValues.put(Form.SignatureField, form.getSignature());
		
		enteredInLoop = false;
		for (FormGroup g : form.getGroups()) {
			for (FormBinding b : g.getBindings()) {
				enteredInLoop = true;
				postValues.put(b.getName(), createCorrectValueForFormBinding(b.getValueType(),b.getMaxLength(),b.getValidatorClass()).toString());
			}
		}
		assertTrue(enteredInLoop);
		
		r = new BodyParamMockRequest(form.getActionURL(), postValues);
		//call decoder
		feedback = validateForm(aesKey, signSecret, r, rs);
		//assert results
		assertTrue(feedback.getRedirect()==null);
		assertTrue(feedback.getParameter().getClass().equals(ExampleModel.class));
		ExampleModel formModel = (ExampleModel) feedback.getParameter();
		
		enteredInLoop = false;
		for (FieldGroup g : createFields().groups()) {
			for (Field b : g.getFields()) {
				enteredInLoop = true;
				Object value = b.getValue(formModel);
				Object expectedValue = createCorrectValueForFormBinding(b.getValueType(),b.getMaxLength(),b.getValidatorClass());
				if(!value.equals(expectedValue)) {
					System.out.println(value+"!="+expectedValue);
				}
				assertTrue(value.equals(expectedValue));
			}
		}
		assertTrue(enteredInLoop);
	}

	private TransformFeedback validateForm(AESGeneratedKey aesKey, String signSecret, Request r, RoutingService rs) {
		BindingProxy proxy = new BindingProxy();
		//create decoder
		ValidatorService validatorService = new DependencyInjectingValidatorService(new MockDependencyService(), new MockTranslator());
		FormDecoder decoder = new FormDecoder(validatorService, new MockPersistenceProvider(), new MockDatabaseConfigurationSource(), aesKey, signSecret, rs);
		TransformFeedback feedback = new TransformFeedback();
		//call decoder
		decoder.apply(
				ExampleModel.class, 
				//get instance of FormModel annotation
				new Annotation[]{proxy.newBinding(proxy.byObject(this).action(null)).getBoundMethod().getParameterAnnotations()[0][0]}, 
				null, 
				r, 
				new MockResponse(), 
				feedback
			);
		
		return feedback;
	}
	
	public ActionResult orig(FormValidation fv) {
		return null;
	}
	
	public ActionResult action(@FormModel ExampleModel model) {
		return null;
	}
	
	public FieldSet createFields() {
		BindingProxy proxy = new BindingProxy();
		ExampleModel model = proxy.byClass(ExampleModel.class);
		
		return new FieldSet().getDefaultGroup().add(
			proxy.newField(model.getAge()),
			proxy.newField(model.getName()),
			proxy.newField(model.getLastname()),
			proxy.newField(model.getSavings())
		).finishGroup();
		
	}
	
	private Object createCorrectValueForFormBinding(Class<?> valueType, int maxLength, Class<? extends Validator> vc) {
		if(valueType.equals(String.class)) {
			return "abc";
		}
		else if(valueType.equals(Double.class) || valueType.equals(double.class)) {
			return new Double(5);
		}
		else if(valueType.equals(Float.class) || valueType.equals(float.class)) {
			return new Float(5);
		}
		else if(valueType.equals(Integer.class) || valueType.equals(int.class)) {
			return new Integer(5);
		}
		else if(valueType.equals(Short.class) || valueType.equals(short.class)) {
			return new Short((short)5);
		}
		throw new RuntimeException("foo");
	}
	
	private String getIncorrectValueForFormBinding(FormBinding b) {
		if(!b.getValueType().equals(String.class)) {
			return "abc";
		}
		else {
			return "";
		}
	}
	
	
	class TransformFeedback implements ActionParameterTransformFeedback {

		private Object cache;
		private Object parameter;
		private ControllerDescription redirect;
		
		@Override
		public void addCacheValue(String name, Object value) {
			cache = value;
		}

		@Override
		public <T> T getCacheValue(String name, Class<T> type) {
			return (T) cache;
		}

		@Override
		public void redirect(ControllerDescription cd) {
			redirect = cd;
		}

		public Object getCache() {
			return cache;
		}

		public Object getParameter() {
			return parameter;
		}

		public ControllerDescription getRedirect() {
			return redirect;
		}

		@Override
		public void setParameter(Object value) {
			parameter = value;
		}
		
	}
}
